package com.ustc.hewei.rpc.config;

import com.ustc.hewei.rpc.annotation.InjectService;
import com.ustc.hewei.rpc.annotation.RPCService;
import com.ustc.hewei.rpc.client.ClientProxyFactory;
import com.ustc.hewei.rpc.server.net.RpcServer;
import com.ustc.hewei.rpc.server.ServiceObject;
import com.ustc.hewei.rpc.server.register.ServiceRegister;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Objects;

/**
 * @author hewei
 * @version 1.0
 * @description: 当初始化完成时，需要执行的步骤，所以实现了ApplicationListener<ContextRefreshedEvent>
 * @date 2022/7/26 19:58
 */

@Slf4j
public class DefaultRpcProcessor implements ApplicationListener<ContextRefreshedEvent> {

    @Resource
    private ServiceRegister serviceRegister;

    @Resource
    private RpcServer rpcServer;

    @Resource
    private ClientProxyFactory clientProxyfactory;

    /*
     * @description: 当项目初始化完成时，会进入下面的方法执行
     * @param event 事件，可以获取上下文对象
     * @author: hewei
     * @date: 2022/7/26 20:06
     **/
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 此项判断的目的是因为在web项目中会有两个容器，分别是root application context以及
        // projectName-servlet context容器（它是root容器的子容器），如果不进行判断，就会分别在两个容器初始化完成均执行
        // 所以下面的判断使得只针对root容器，因为它没有父容器了
        // https://blog.csdn.net/tanksyg/article/details/83863669
        if (event.getApplicationContext().getParent() == null) {
            ApplicationContext context = event.getApplicationContext();
            registerRpcServer(context);
            injectRpcService(context);
        }
    }

    /*
     * @description: 对于添加了@RPCService注解的类进行注册的方法
     * @param context 上下文对象，可以从中获取到添加了任意注解的所有对象
     * @author: hewei
     * @date: 2022/7/26 21:49
     **/
    private void registerRpcServer(ApplicationContext context) {
        // 获得上下文中添加了RPCService注解的类对象
        Map<String, Object> beans = context.getBeansWithAnnotation(RPCService.class);
        // 遍历所有的类对象
        for (Object bean : beans.values()) {
            try {
                Class<?> clazz = bean.getClass();
                // 获得对象的所有接口组成的数组
                Class<?>[] interfaces = clazz.getInterfaces();
                ServiceObject serviceObject;
                // 如果只继承了一个接口
                if (interfaces.length == 1) {
                    //将接口名进行拼接，作为放入注册中心的键值
                    serviceObject = new ServiceObject(interfaces[0].getName(), interfaces[0], bean);
                } else {
                    // 没继承接口或者是继承了多个接口，则将对象的全类名进行拼接，作为注册中心的键值
                    String serviceName = clazz.getName();
                    // 如果获得的名字是空，则抛出异常
                    if ("".equals(serviceName)) {
                        log.error("注册服务 {} 时，获取全类名失败", bean.getClass().getName());
                        throw new RuntimeException("获取全类名失败!");
                    }
                    // 不为空则创建ServiceObject对象
                    serviceObject = new ServiceObject(serviceName, Class.forName(serviceName), bean);
                }
                // 交由自动注入的ServiceRegister对象进行注册
                serviceRegister.register(serviceObject);
            } catch (ClassNotFoundException | UnknownHostException e) {
                e.printStackTrace();
            }
        }
        if (beans.size() != 0) rpcServer.start();
    }

    public void injectRpcService(ApplicationContext context) {
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            Class<?> clazz = context.getType(name);
            if (Objects.isNull(clazz)) continue;
            for (Field field : clazz.getDeclaredFields()) {
                InjectService annotation = field.getAnnotation(InjectService.class);
                if (Objects.isNull(annotation)) continue;
                Object bean = context.getBean(name);
                Class<?> fieldType = field.getType();
                field.setAccessible(true);
                try {
                    field.set(bean, clientProxyfactory.getProxy(fieldType));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
